support optional start elements in xmlstreamwriter. (#1193)
authortsteven4 <13596209+tsteven4@users.noreply.github.com>
Fri, 27 Oct 2023 20:07:24 +0000 (14:07 -0600)
committerGitHub <noreply@github.com>
Fri, 27 Oct 2023 20:07:24 +0000 (14:07 -0600)
this adds methods to xmlstreamwriter to allow conditional writing
of a start element.  The conditional element will only be written to
the stream if it has children, which is deteremined when the condtional
element is ended.  This simplifies writing of such elements as the
writer does not need to know if it will subsequently write children or
not.

garmin_fs.cc
garmin_fs.h
gpx.cc
src/core/xmlstreamwriter.cc
src/core/xmlstreamwriter.h

index 1827fe1208829f2d6c824af28b118770f6d77b11..c6846efcec0c8277d72db7dd53483aa0df9a55a2 100644 (file)
 
  */
 
-#include <cstdio>                    // for snprintf, sscanf
-#include <cstdlib>                   // for strtod
+#include <cstdio>                      // for snprintf, sscanf
+#include <cstdlib>                     // for strtod
 
-#include <QString>                   // for QString, QStringLiteral
-#include <QXmlStreamWriter>          // for QXmlStreamWriter
-#include <Qt>                        // for CaseInsensitive
+#include <QString>                     // for QString, QStringLiteral
+#include <Qt>                          // for CaseInsensitive
 
 #include "defs.h"
 #include "garmin_fs.h"
-#include "garmin_tables.h"           // for gt_switch_display_mode_value, gt_display_mode_symbol, gt_display_mode_symbol_and_comment, gt_display_mode_symbol_and_name
-#include "inifile.h"                 // for inifile_readstr
+#include "garmin_tables.h"             // for gt_switch_display_mode_value, gt_display_mode_symbol, gt_display_mode_symbol_and_comment, gt_display_mode_symbol_and_name
+#include "inifile.h"                   // for inifile_readstr
+#include "src/core/xmlstreamwriter.h"  // for XmlStreamWriter
 
 
 #define MYNAME "garmin_fs"
@@ -80,113 +80,75 @@ garmin_fs_t::~garmin_fs_t()
 
 void
 garmin_fs_xml_fprint(const Waypoint* waypt,
-                     QXmlStreamWriter* writer)
+                     gpsbabel::XmlStreamWriter* writer)
 {
   garmin_fs_t* gmsd = garmin_fs_t::find(waypt);
 
-  if (gmsd == nullptr) {
-    return;
-  }
+  writer->stackOptionalStartElement(QStringLiteral("extensions"));
+  writer->stackOptionalStartElement(QStringLiteral("gpxx:WaypointExtension"));
+  writer->stackNamespace(QStringLiteral("http://www.garmin.com/xmlschemas/GpxExtensions/v3"),
+                         "gpxx");
 
-  /* Find out if there is at least one field set */
-  QString addr = garmin_fs_t::get_addr(gmsd, "");
-  if (addr.isEmpty()) {
-    addr = garmin_fs_t::get_city(gmsd, "");
-  }
-  if (addr.isEmpty()) {
-    addr = garmin_fs_t::get_country(gmsd, "");
+  if (waypt->proximity_has_value()) {
+    writer->stackTextElement(QStringLiteral("gpxx:Proximity"), QString::number(waypt->proximity_value(), 'f', 6));
   }
-  if (addr.isEmpty()) {
-    addr = garmin_fs_t::get_postal_code(gmsd, "");
+
+  if (waypt->temperature_has_value()) {
+    writer->stackTextElement(QStringLiteral("gpxx:Temperature"), QString::number(waypt->temperature_value(), 'f', 6));
   }
-  if (addr.isEmpty()) {
-    addr = garmin_fs_t::get_state(gmsd, "");
+
+  if (waypt->depth_has_value()) {
+    writer->stackTextElement(QStringLiteral("gpxx:Depth"), QString::number(waypt->depth_value(), 'f', 6));
   }
 
-  QString phone = garmin_fs_t::get_phone_nr(gmsd, "");
-
-  if (!addr.isEmpty() || !phone.isEmpty() ||
-      (gmsd->flags.category && gmsd->category) ||
-      waypt->depth_has_value() ||
-      waypt->proximity_has_value() ||
-      waypt->temperature_has_value() ||
-      gmsd->flags.display) {
-    writer->writeStartElement(QStringLiteral("extensions"));
-    writer->writeStartElement(QStringLiteral("gpxx:WaypointExtension"));
-    writer->writeNamespace(QStringLiteral("http://www.garmin.com/xmlschemas/GpxExtensions/v3"),
-                           "gpxx");
-    if (waypt->proximity_has_value()) {
-      writer->writeTextElement(QStringLiteral("gpxx:Proximity"), QString::number(waypt->proximity_value(), 'f', 6));
-    }
-    if (waypt->temperature_has_value()) {
-      writer->writeTextElement(QStringLiteral("gpxx:Temperature"),  QString::number(waypt->temperature_value(), 'f', 6));
-    }
-    if (waypt->depth_has_value()) {
-      writer->writeTextElement(QStringLiteral("gpxx:Depth"), QString::number(waypt->depth_value(), 'f', 6));
-    }
-    if (gmsd->flags.display) {
-      const char* cx;
-      switch (gmsd->display) {
-      case gt_display_mode_symbol:
-        cx = "SymbolOnly";
-        break;
-      case gt_display_mode_symbol_and_comment:
-        cx = "SymbolAndDescription";
-        break;
-      default:
-        cx = "SymbolAndName";
-        break;
-      }
-      writer->writeTextElement(QStringLiteral("gpxx:DisplayMode"), cx);
+  if (garmin_fs_t::has_display(gmsd)) {
+    const char* cx;
+    switch (gmsd->display) {
+    case gt_display_mode_symbol:
+      cx = "SymbolOnly";
+      break;
+    case gt_display_mode_symbol_and_comment:
+      cx = "SymbolAndDescription";
+      break;
+    default:
+      cx = "SymbolAndName";
+      break;
     }
-    if (gmsd->flags.category && gmsd->category) {
-      uint16_t cx = gmsd->category;
-      writer->writeStartElement(QStringLiteral("gpxx:Categories"));
-      for (int i = 0; i < 16; i++) {
-        if (cx & 1) {
-          writer->writeTextElement(QStringLiteral("gpxx:Category"), QStringLiteral("Category %1").arg(i+1));
-        }
-        cx = cx >> 1;
-      }
-      writer->writeEndElement(); // gpxx:Categories
-    }
-    if (!addr.isEmpty()) {
-      QString str;
-      writer->writeStartElement(QStringLiteral("gpxx:Address"));
+    writer->stackTextElement(QStringLiteral("gpxx:DisplayMode"), cx);
+  }
 
-      if (!(str = garmin_fs_t::get_addr(gmsd, nullptr)).isEmpty()) {
-        writer->writeTextElement(QStringLiteral("gpxx:StreetAddress"), str);
-      }
-      if (!(str = garmin_fs_t::get_city(gmsd, nullptr)).isEmpty()) {
-        writer->writeTextElement(QStringLiteral("gpxx:City"), str);
-      }
-      if (!(str = garmin_fs_t::get_state(gmsd, nullptr)).isEmpty()) {
-        writer->writeTextElement(QStringLiteral("gpxx:State"), str);
-      }
-      if (!(str = garmin_fs_t::get_country(gmsd, nullptr)).isEmpty()) {
-        writer->writeTextElement(QStringLiteral("gpxx:Country"), str);
+  if (garmin_fs_t::has_category(gmsd)) {
+    uint16_t cx = gmsd->category;
+    writer->stackStartElement(QStringLiteral("gpxx:Categories"));
+    for (int i = 0; i < 16; i++) {
+      if (cx & 1) {
+        writer->stackTextElement(QStringLiteral("gpxx:Category"), QStringLiteral("Category %1").arg(i+1));
       }
-      if (!(str = garmin_fs_t::get_postal_code(gmsd, nullptr)).isEmpty()) {
-        writer->writeTextElement(QStringLiteral("gpxx:PostalCode"), str);
-      }
-      writer->writeEndElement(); // /gpxx::Address
+      cx = cx >> 1;
     }
+    writer->stackEndElement(); // gpxx:Categories
+  }
 
-    if (!phone.isEmpty()) {
-      writer->writeTextElement(QStringLiteral("gpxx:PhoneNumber"), phone);
-    }
+  writer->stackOptionalStartElement(QStringLiteral("gpxx:Address"));
+  writer->stackOptionalTextElement(QStringLiteral("gpxx:StreetAddress"), garmin_fs_t::get_addr(gmsd, nullptr));
+  writer->stackOptionalTextElement(QStringLiteral("gpxx:City"), garmin_fs_t::get_city(gmsd, nullptr));
+  writer->stackOptionalTextElement(QStringLiteral("gpxx:State"), garmin_fs_t::get_state(gmsd, nullptr));
+  writer->stackOptionalTextElement(QStringLiteral("gpxx:Country"), garmin_fs_t::get_country(gmsd, nullptr));
+  writer->stackOptionalTextElement(QStringLiteral("gpxx:PostalCode"), garmin_fs_t::get_postal_code(gmsd, nullptr));
+  writer->stackEndElement(); // gpxx:Address
+
+  writer->stackOptionalTextElement(QStringLiteral("gpxx:PhoneNumber"), garmin_fs_t::get_phone_nr(gmsd, nullptr));
 
-    writer->writeEndElement(); // /gpxx::WaypointExtension
-    writer->writeEndElement(); // /extensions.
-  }
 
+  writer->stackEndElement(); // gpxx:WaypointExtension
+  writer->stackEndElement(); // extensions
 }
 
 void
 garmin_fs_xml_convert(const int base_tag, int tag, const QString& qstr, Waypoint* waypt)
 {
   // FIXME: eliminate C string copy/use here:
-  const char *cdatastr = xstrdup(qstr);
+  const charcdatastr = xstrdup(qstr);
   garmin_fs_t* gmsd = garmin_fs_t::find(waypt);
   if (gmsd == nullptr) {
     gmsd = garmin_fs_alloc(-1);
index 25d3bbb18c752844d7f95de8a4b910b8b00bd2a0..d225339dc72e7a8f89a6aa9fe625f03cf41b9799 100644 (file)
 #ifndef GARMIN_FS_H
 #define GARMIN_FS_H
 
-#include <cstddef>                  // for size_t
-#include <cstdint>                  // for int32_t, int16_t, uint16_t
+#include <cstdint>                     // for int32_t, int16_t, uint16_t
 
-#include <QString>                  // for QString
-#include <QXmlStreamWriter>         // for QXmlStreamWriter
+#include <QString>                     // for QString
 
 #include "defs.h"
-#include "formspec.h"               // for FsChainFind, kFsGmsd, FormatSpecificData
+#include "formspec.h"                  // for FsChainFind, kFsGmsd, FormatSpecificData
+#include "src/core/xmlstreamwriter.h"  // for XmlStreamWriter
 
 
 /* this order is used by most devices */
@@ -222,7 +221,7 @@ void garmin_fs_copy(void** dest, const void* src);
 
 /* for GPX */
 void garmin_fs_xml_convert(int base_tag, int tag, const QString& qstr, Waypoint* waypt);
-void garmin_fs_xml_fprint(const Waypoint* waypt, QXmlStreamWriter*);
+void garmin_fs_xml_fprint(const Waypoint* waypt, gpsbabel::XmlStreamWriter*);
 
 /* common garmin_fs utilities */
 
diff --git a/gpx.cc b/gpx.cc
index 9a127c7c4c12179a7ecd5083418e99673b2398cc..ad5bfcaaa939118726eca8231403e738ebb8dbc4 100644 (file)
--- a/gpx.cc
+++ b/gpx.cc
@@ -21,6 +21,7 @@
 
 #include "gpx.h"
 
+#include <cassert>                          // for assert
 #include <cmath>                            // for lround
 #include <cstdio>                           // for sscanf
 #include <cstring>                          // for strchr, strncpy
@@ -573,10 +574,10 @@ GpxFormat::gpx_end(QStringView /*unused*/)
     gc_log_date = gpsbabel::DateTime();
     break;
   case tt_cache_favorite_points:
-    wpt_tmp->AllocGCData()->favorite_points  = cdatastr.toInt();
+    wpt_tmp->AllocGCData()->favorite_points = cdatastr.toInt();
     break;
   case tt_cache_personal_note:
-    wpt_tmp->AllocGCData()->personal_note  = cdatastr;
+    wpt_tmp->AllocGCData()->personal_note = cdatastr;
     break;
 
   /*
@@ -1047,7 +1048,7 @@ GpxFormat::qualifiedName() const
 void
 GpxFormat::read()
 {
-  for (bool atEnd = false; !reader->atEnd() && !atEnd;)  {
+  for (bool atEnd = false; !reader->atEnd() && !atEnd;) {
     reader->readNext();
     // do processing
     switch (reader->tokenType()) {
@@ -1097,7 +1098,7 @@ GpxFormat::read()
     }
   }
 
-  if (reader->hasError())  {
+  if (reader->hasError()) {
     fatal(FatalMsg() << MYNAME << "Read error:" << reader->errorString()
           << "File:" << iqfile->fileName()
           << "Line:" << reader->lineNumber()
@@ -1263,87 +1264,80 @@ GpxFormat::gpx_write_common_position(const Waypoint* waypointp, const gpx_point_
 void
 GpxFormat::gpx_write_common_extensions(const Waypoint* waypointp, const gpx_point_type point_type) const
 {
-  // gpx version we are writing is >= 1.1.
-  garmin_fs_t* gmsd = (opt_garminext) ? garmin_fs_t::find(waypointp) : nullptr;  // only needed if garmin extensions selected
+  assert(gpx_write_version >= gpx_1_1);
 
-  if ((opt_humminbirdext && (waypointp->depth_has_value() || waypointp->temperature_has_value())) ||
-      (opt_garminext && gpxpt_route==point_type && gmsd != nullptr && gmsd->ilinks != nullptr)  ||
-      (opt_garminext && gpxpt_waypoint==point_type && (waypointp->proximity_has_value() || waypointp->temperature_has_value() || waypointp->depth_has_value())) ||
-      (opt_garminext && gpxpt_track==point_type && (waypointp->temperature_has_value() || waypointp->depth_has_value() || waypointp->heartrate != 0 || waypointp->cadence != 0))) {
-    writer->writeStartElement(QStringLiteral("extensions"));
 
-    if (opt_humminbirdext) {
-      if (waypointp->depth_has_value()) {
-        writer->writeTextElement(QStringLiteral("h:depth"), toString(waypointp->depth_value() * 100.0));
+  writer->stackOptionalStartElement(QStringLiteral("extensions"));
+
+  if (opt_humminbirdext) {
+    if (waypointp->depth_has_value()) {
+      writer->stackTextElement(QStringLiteral("h:depth"), toString(waypointp->depth_value() * 100.0));
+    }
+    if (waypointp->temperature_has_value()) {
+      writer->stackTextElement(QStringLiteral("h:temperature"), toString(waypointp->temperature_value()));
+    }
+  }
+
+  if (opt_garminext) {
+    // Although not required by the schema we assume that gpxx:WaypointExtension must be a child of gpx:wpt.
+    // Although not required by the schema we assume that gpxx:RoutePointExtension must be a child of gpx:rtept.
+    // Although not required by the schema we assume that gpxx:TrackPointExtension must be a child of gpx:trkpt.
+    // Although not required by the schema we assume that gpxtpx:TrackPointExtension must be a child of gpx:trkpt.
+    switch (point_type) {
+    case gpxpt_waypoint:
+      writer->stackOptionalStartElement(QStringLiteral("gpxx:WaypointExtension"));
+      if (waypointp->proximity_has_value()) {
+        writer->stackTextElement(QStringLiteral("gpxx:Proximity"), toString(waypointp->proximity_value()));
       }
       if (waypointp->temperature_has_value()) {
-        writer->writeTextElement(QStringLiteral("h:temperature"), toString(waypointp->temperature_value()));
+        writer->stackTextElement(QStringLiteral("gpxx:Temperature"), toString(waypointp->temperature_value()));
       }
-    }
-
-    if (opt_garminext) {
-      // Although not required by the schema we assume that gpxx:WaypointExtension must be a child of gpx:wpt.
-      // Although not required by the schema we assume that gpxx:RoutePointExtension must be a child of gpx:rtept.
-      // Although not required by the schema we assume that gpxx:TrackPointExtension  must be a child of gpx:trkpt.
-      // Although not required by the schema we assume that gpxtpx:TrackPointExtension must be a child of gpx:trkpt.
-      switch (point_type) {
-      case gpxpt_waypoint:
-        if (waypointp->proximity_has_value() || waypointp->temperature_has_value() || waypointp->depth_has_value()) {
-          writer->writeStartElement(QStringLiteral("gpxx:WaypointExtension"));
-          if (waypointp->proximity_has_value()) {
-            writer->writeTextElement(QStringLiteral("gpxx:Proximity"), toString(waypointp->proximity_value()));
-          }
-          if (waypointp->temperature_has_value()) {
-            writer->writeTextElement(QStringLiteral("gpxx:Temperature"), toString(waypointp->temperature_value()));
-          }
-          if (waypointp->depth_has_value()) {
-            writer->writeTextElement(QStringLiteral("gpxx:Depth"), toString(waypointp->depth_value()));
-          }
-          writer->writeEndElement(); // "gpxx:WaypointExtension"
-        }
-        break;
-      case gpxpt_route:
-        if (gmsd != nullptr && gpxpt_route==point_type && gmsd->ilinks != nullptr) {
-          writer->writeStartElement(QStringLiteral("gpxx:RoutePointExtension"));
-          garmin_ilink_t* link = gmsd->ilinks;
-          garmin_ilink_t* prior = nullptr;  // GDB files sometime contain repeated point; omit them
-          while (link != nullptr) {
-            if (prior == nullptr || prior->lat != link->lat || prior->lon != link->lon) {
-              writer->writeStartElement(QStringLiteral("gpxx:rpt"));
-              writer->writeAttribute(QStringLiteral("lat"), toString(link->lat));
-              writer->writeAttribute(QStringLiteral("lon"), toString(link->lon));
-              writer->writeEndElement(); // "gpxx:rpt"
-            }
-            prior = link;
-            link = link->next;
-          }
-          writer->writeEndElement(); // "gpxx:RoutePointExtension"
-        }
-        break;
-      case gpxpt_track:
-        if (waypointp->temperature_has_value() || waypointp->depth_has_value() || waypointp->heartrate != 0 || waypointp->cadence != 0) {
-          // gpxtpx:TrackPointExtension is a replacement for gpxx:TrackPointExtension.
-          writer->writeStartElement(QStringLiteral("gpxtpx:TrackPointExtension"));
-          if (waypointp->temperature_has_value()) {
-            writer->writeTextElement(QStringLiteral("gpxtpx:atemp"), toString(waypointp->temperature_value()));
-          }
-          if (waypointp->depth_has_value()) {
-            writer->writeTextElement(QStringLiteral("gpxtpx:depth"), toString(waypointp->depth_value()));
-          }
-          if (waypointp->heartrate != 0) {
-            writer->writeTextElement(QStringLiteral("gpxtpx:hr"), QString::number(waypointp->heartrate));
-          }
-          if (waypointp->cadence != 0) {
-            writer->writeTextElement(QStringLiteral("gpxtpx:cad"), QString::number(waypointp->cadence));
+      if (waypointp->depth_has_value()) {
+        writer->stackTextElement(QStringLiteral("gpxx:Depth"), toString(waypointp->depth_value()));
+      }
+      writer->stackEndElement(); // gpxx:WaypointExtension
+      break;
+    case gpxpt_route: {
+      garmin_fs_t* gmsd = garmin_fs_t::find(waypointp);
+      if (gmsd != nullptr && gmsd->ilinks != nullptr) {
+        writer->stackOptionalStartElement(QStringLiteral("gpxx:RoutePointExtension"));
+        garmin_ilink_t* link = gmsd->ilinks;
+        garmin_ilink_t* prior = nullptr; // GDB files sometime contain repeated point; omit them
+        while (link != nullptr) {
+          if (prior == nullptr || prior->lat != link->lat || prior->lon != link->lon) {
+            writer->stackStartElement(QStringLiteral("gpxx:rpt"));
+            writer->stackAttribute(QStringLiteral("lat"), toString(link->lat));
+            writer->stackAttribute(QStringLiteral("lon"), toString(link->lon));
+            writer->stackEndElement(); // "gpxx:rpt"
           }
-          writer->writeEndElement(); // "gpxtpx:TrackPointExtension"
+          prior = link;
+          link = link->next;
         }
-        break;
+        writer->stackEndElement(); // gpxx:RoutePointExtension
       }
     }
-
-    writer->writeEndElement(); // "extensions"
+    break;
+    case gpxpt_track:
+      // gpxtpx:TrackPointExtension is a replacement for gpxx:TrackPointExtension.
+      writer->stackOptionalStartElement(QStringLiteral("gpxtpx:TrackPointExtension"));
+      if (waypointp->temperature_has_value()) {
+        writer->stackTextElement(QStringLiteral("gpxtpx:atemp"), toString(waypointp->temperature_value()));
+      }
+      if (waypointp->depth_has_value()) {
+        writer->stackTextElement(QStringLiteral("gpxtpx:depth"), toString(waypointp->depth_value()));
+      }
+      if (waypointp->heartrate != 0) {
+        writer->stackTextElement(QStringLiteral("gpxtpx:hr"), QString::number(waypointp->heartrate));
+      }
+      if (waypointp->cadence != 0) {
+        writer->stackTextElement(QStringLiteral("gpxtpx:cad"), QString::number(waypointp->cadence));
+      }
+      writer->stackEndElement(); // gpxtpx:TrackPointExtension
+      break;
+    }
   }
+
+  writer->stackEndElement(); // "extensions"
 }
 
 void
index 307dc6ffba6642fee8b9f486fb0a0a3843a92cda..1ce72f1c30ae77b8a1347d85df8144a929818636 100644 (file)
@@ -23,6 +23,8 @@
 #include <QXmlStreamWriter>         // for QXmlStreamWriter
 #include <QtGlobal>                 // for QT_VERSION, QT_VERSION_CHECK
 
+#include "defs.h"
+
 // As this code began in C, we have several hundred places that write
 // c strings.  Add a test that the string contains anything useful
 // before serializing an empty tag.
 
 namespace gpsbabel
 {
-// Dont emit the element if there's nothing interesting in it.
+XmlStreamWriter::xml_stack_list_entry_t& XmlStreamWriter::activeStack()
+{
+  if (stack_list.isEmpty()) {
+    fatal("xmlstreamwriter: programming error: the stack* functions are used incorrectly.");
+  }
+  return stack_list.last();
+}
+
+void XmlStreamWriter::stackAttribute(const QString& name, const QString& value)
+{
+  activeStack().append(xml_command(xml_wrt_cmd_t::attribute, name, value));
+}
+
+void XmlStreamWriter::stackEndElement()
+{
+  xml_stack_list_entry_t& active_stack = activeStack();
+  active_stack.append(xml_command(xml_wrt_cmd_t::end_element));
+
+  // Has the active_stack OptionalStartElement been paired with an EndElement?
+  if (active_stack.element_depth == 0) { // yes
+    const xml_stack_list_entry_t completed_stack = stack_list.takeLast();
+    // Does the completed_stack OptionalStartElement have any content?
+    if (completed_stack.element_count > 1) { // yes
+      // Is this the initial OptionalStartElement?
+      if (!stack_list.isEmpty()) { // no.  append stack contents to parent.
+        stack_list.last().append(completed_stack);
+      } else { // yes.  write the stack contents.
+        for (const auto& command: completed_stack.stack) {
+          switch (command.type) {
+          case xml_wrt_cmd_t::start_element:
+            QXmlStreamWriter::writeStartElement(command.name);
+            break;
+          case xml_wrt_cmd_t::attribute:
+            QXmlStreamWriter::writeAttribute(command.name, command.value);
+            break;
+          case xml_wrt_cmd_t::name_space:
+            QXmlStreamWriter::writeNamespace(command.name, command.value);
+            break;
+          case xml_wrt_cmd_t::text_element:
+            QXmlStreamWriter::writeTextElement(command.name, command.value);
+            break;
+          case xml_wrt_cmd_t::end_element:
+            QXmlStreamWriter::writeEndElement();
+            break;
+          }
+        }
+      }
+    } // else {no.  empty OptionalStartElement is discarded.}
+  }
+}
+
+void XmlStreamWriter::stackNamespace(const QString& namespaceUri, const QString& prefix)
+{
+  activeStack().append(xml_command(xml_wrt_cmd_t::name_space, namespaceUri, prefix));
+}
+
+/*
+ * Start an element that will be written if and only if it has children.
+ *
+ * Usage:
+ * 1. stackOptionalStartElement must be the first stack*() method called.
+ * 2. stackOptionalStartElement must be paired with a subsequent
+ *    stackEndElement.
+ * 3. write*() methods should not be called until the initial optional start
+ *    element has paired with a subsequent stackEndElement.
+ */
+void XmlStreamWriter::stackOptionalStartElement(const QString& name)
+{
+  stack_list.append(xml_stack_list_entry_t());
+  stackStartElement(name);
+}
+
+void XmlStreamWriter::stackOptionalTextElement(const QString& name, const QString& text)
+{
+  if (!text.isEmpty()) {
+    stackTextElement(name, text);
+  }
+}
+
+void XmlStreamWriter::stackStartElement(const QString& name)
+{
+  activeStack().append(xml_command(xml_wrt_cmd_t::start_element, name));
+}
+
+void XmlStreamWriter::stackTextElement(const QString& name, const QString& text)
+{
+  activeStack().append(xml_command(xml_wrt_cmd_t::text_element, name, text));
+}
+
+// Don't emit the element if there's nothing interesting in it.
 void XmlStreamWriter::writeOptionalTextElement(const QString& qualifiedName, const QString& text)
 {
   if (!text.isEmpty()) {
index 24097da6b8e619bfa4b645d5ffbf1fd2f11cb54a..87b236e808fbc37aa9554c1fcb6bf668ee452ea2 100644 (file)
 #ifndef XMLSTREAMWRITER_H
 #define XMLSTREAMWRITER_H
 
-#include <QString>                  // for QString
-#include <QXmlStreamWriter>         // for QXmlStreamWriter
+#include <QList>             // for QList
+#include <QString>           // for QString
+#include <QXmlStreamWriter>  // for QXmlStreamWriter
+#include <utility>
 
 namespace gpsbabel
 {
@@ -31,7 +33,82 @@ class XmlStreamWriter : public QXmlStreamWriter
 public:
   using QXmlStreamWriter::QXmlStreamWriter;
 
+  /* Member Functions */
+
+  void stackAttribute(const QString& name, const QString& value);
+  void stackEndElement();
+  void stackNamespace(const QString& namespaceUri, const QString& prefix);
+  void stackOptionalStartElement(const QString& name);
+  void stackOptionalTextElement(const QString& name, const QString& text);
+  void stackStartElement(const QString& name);
+  void stackTextElement(const QString& name, const QString& text);
+
   void writeOptionalTextElement(const QString& qualifiedName, const QString& text);
+
+private:
+  /* Types */
+
+  enum class xml_wrt_cmd_t {
+    start_element,
+    attribute,
+    name_space,
+    text_element,
+    end_element
+  };
+
+  struct xml_command {
+    explicit xml_command(xml_wrt_cmd_t t,
+                         QString n = QString(),
+                         QString v = QString())
+      : type(t), name(std::move(n)), value(std::move(v)) {};
+
+    xml_wrt_cmd_t type;
+    QString name;
+    QString value;
+  };
+
+  using xml_stack_t = QList<xml_command>;
+
+  struct xml_stack_list_entry_t {
+    void append(const xml_stack_list_entry_t& other)
+    {
+      stack.append(other.stack);
+      element_count += other.element_count;
+    };
+
+    void append(const xml_command& cmd)
+    {
+      stack.append(cmd);
+      switch (cmd.type) {
+      case xml_wrt_cmd_t::start_element:
+        ++element_count;
+        ++element_depth;
+        break;
+      case xml_wrt_cmd_t::text_element:
+        ++element_count;
+        break;
+      case xml_wrt_cmd_t::end_element:
+        --element_depth;
+        break;
+      case xml_wrt_cmd_t::attribute:
+      case xml_wrt_cmd_t::name_space:
+        break;
+      }
+    };
+
+    xml_stack_t stack;
+    int element_count{0};
+    int element_depth{0};
+  };
+
+  /* Member Functions */
+
+  xml_stack_list_entry_t& activeStack();
+
+  /* Data Members */
+
+  QList<xml_stack_list_entry_t> stack_list;
+
 };
 
 } // namespace gpsbabel